package com.jse.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Map;
import java.util.Map.Entry;

import com.jse.web.Ctx;


public class HttpWebSocket {

	protected static final int MAX_BUFFER_LENGTH = 512;

	protected Ctx socket;
	protected OutputStream out;
	protected InputStream in;
	protected Map<String, String> clientFields;
	protected ArrayList<Listener> listeners = new ArrayList<Listener>();
	protected volatile boolean isOpen = true;

	protected Map<String, String> serverFields;

	public HttpWebSocket(Ctx socket,Map<String, String> clientFields,Map<String, String> serverFields)
			throws IOException {
		this.socket = socket;
		this.clientFields = clientFields;
		this.serverFields = serverFields;

		out = socket.getResponseBody();
		in = socket.getRequestBody();
	}

	public void sendMessage(String message) throws IOException{
		byte[] buffer = message.getBytes("UTF-8");
		out.write(0x81);//the only frame, opcode=text
		if(buffer.length < 126){
			out.write(buffer.length);
		}else if(buffer.length < 0xffff){
			out.write(0x7e);
			out.write(buffer.length>>8);
			out.write(buffer.length);
		}else{
			out.write(0x7f);
			out.write(buffer.length>>24);
			out.write(buffer.length>>16);
			out.write(buffer.length>>8);
			out.write(buffer.length);
		}
		out.write(buffer);
		out.flush();
	}

	public void listen() {
		try {
			int length = 0;
			int b;
			String inputLine = "";
			Charset utf = Charset.forName("UTF-8");
			CharsetDecoder decoder = utf.newDecoder();
			byte[] buffer = new byte[MAX_BUFFER_LENGTH];
			ByteBuffer bytes;
			onOpen();
			while (isOpen) {
				decoder.reset();
				inputLine = "";
				boolean notFinished = true;
				
				int opcode;
				boolean masking;
				int[] mask = new int[4];
				while(notFinished){
					b = in.read();
					if(b==-1)continue;
					notFinished = ((b & 0x80) == 0x00);
					System.out.println("\n==New frame==");
					opcode = (0x0f & b);
					System.out.println("Opcode: "+opcode);
					if(opcode == 0x08){
						isOpen = false;
						break;
					}
					length = (0xff & in.read());
					masking = ((length & 0x80) == 0x80);
					length = (0x7F & length);
					if(length == 0x7F){
						length = (0xff & in.read())<<24;
						length |= (0xff & in.read())<<16;
						length |= (0xff & in.read())<<8;
						length |= (0xff & in.read());
					}else if(length == 0x7E){
						length = (0xff & in.read())<<8;
						length |= (0xff & in.read());
					}
					System.out.println("Masking: "+masking+", Length: "+length);
					if(masking){
						mask[0] = (0xff & in.read());
						mask[1] = (0xff & in.read());
						mask[2] = (0xff & in.read());
						mask[3] = (0xff & in.read());
					}else{
						mask[0] = 0;
						mask[1] = 0;
						mask[2] = 0;
						mask[3] = 0;
					}
					for(int i=0; i<length; i++){
						buffer[i] = (byte) ((0xff & in.read()) ^ mask[i%4]);
						if(i==MAX_BUFFER_LENGTH){
							bytes = ByteBuffer.wrap(buffer);
							inputLine += decoder.decode(bytes).toString();
							length -= i;
							i = 0;
						}
					}
				}
				if(length == -1){
					isOpen = false;
				}
				if(isOpen){
					bytes = ByteBuffer.wrap(buffer, 0, length);
					inputLine += decoder.decode(bytes).toString();
					System.out.println("inp:"+inputLine);
					onMessage(inputLine);
				}
			}
			
			
			out.close();
			in.close();
//			socket.close();

		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			System.out.println("Connection closed");
			onClose();
		}

	}

	public void closeConnection() {
		isOpen = false;
		try {
			out.close();
			in.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void addEventListener(Listener listener) {
		listeners.add(listener);
	}

	public void removeEventListener(Listener listener) {
		listeners.remove(listener);
	}

	public String getProperty(String name) {
		return clientFields.get(name);
	}
	
	protected void onOpen() {
		for (int i = 0; i < listeners.size(); i++) {
			listeners.get(i).onOpen(this);
		}
	}

	protected void onClose() {
		for (int i = 0; i < listeners.size(); i++) {
			listeners.get(i).onClose(this);
		}
	}

	protected void onMessage(String message) {
		for (int i = 0; i < listeners.size(); i++) {
			listeners.get(i).onMessage(message, this);
		}
	}
	
	public boolean handshake() throws IOException {
		
		String header = "";
		header += "HTTP/1.1 101 Switching Protocols\r\n";
		header += "Upgrade: WebSocket\r\n";
		header += "Connection: Upgrade\r\n";
		header += "Sec-WebSocket-Accept: "+ accept(clientFields.get("sec-websocket-key"))+ "\r\n";
		if (clientFields.containsKey("sec-websocket-protocol")) {
			header += "Sec-WebSocket-Protocol: "
					+ clientFields.get("sec-websocket-protocol") + "\r\n";
		}
		for (Entry<String, String> field : serverFields.entrySet()) {
			header += field.getKey() + ": " + field.getValue() + "\r\n";
		}
		header += "\r\n";
		out.write(header.getBytes("UTF-8"));
		out.flush();
		System.out.println("Creating WebSocket"+in.available());
		return true;
	}
	
	private static final String GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
	private static String accept(String key) {
		try {
			return Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1")
					.digest((key + GUID).getBytes("UTF-8")));
		} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}
	
	
	
	public interface Listener {
		default void onOpen(HttpWebSocket ws) {
			System.out.print("open");
		}
		public void onMessage(String msg, HttpWebSocket ws);
		public void onClose(HttpWebSocket ws);
	}

}
